package com.heo.queue.task;

import java.net.InetSocketAddress;
import java.text.DateFormat;
import java.util.Date;

import org.apache.log4j.Logger;

import com.heo.pojo.ControlInfoEntity;
import com.heo.pojo.DeviceSendPacketHistoryEntity;
import com.heo.protocol.packet.BasePacket;
import com.heo.protocol.packet.DeviceCommand;
import com.heo.pub.ApplicationContextUtil;
import com.heo.pub.DictEnum;
import com.heo.pub.ServerConfig;
import com.heo.queue.TaskQueue;
import com.heo.server.SocketChannelMap;
import com.heo.service.DeviceSendPacketHistoryService;
import com.heo.util.ByteHelper;
import com.heo.util.NettyChannelMapUtils;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;

/**
 * 向设备发送消息的处理线程
 */

public class SendTaskThread implements Runnable
{
  private static Logger log = Logger.getLogger(SendTaskThread.class);
  private static String THREAD_NAME = "[发送指令线程]";

  private static final int SLEEP_TIME = 2000;// 每次休息1000毫秒
  private static final int WAITING_TIME = 10000;// 模块收到启动指令，等待启动成功时间
  private static boolean startSuccessWaiting = false;//是否进入二次发送启动指令线程

  public SendTaskThread()
  {

  }

  @Override
  public void run()
  {
    try
    {
      sendTask();
    }
    catch (Exception e)
    {
      log.error(e.getMessage(), e);
      log.error(THREAD_NAME + "[推送]线程出现严重异常，已退出运行。");
    }
  }

  public void sendTask() throws Exception
  {
    log.debug(THREAD_NAME + "[推送]线程开始执行。");

    SendTask sendTask = null;
    // 这里不能先判断队列 长度，然后再来取出，在多线程环境下，会造成判断长度不为0，但是实际取出的时候，已经被其他线程取走的情况
    while ((sendTask = TaskQueue.pollSendTask()) != null)
    {
      Thread.sleep(SLEEP_TIME);
      String channelId = null;
      String remoteIpAddress = null;
      String logHeader = null;
      String sendJson = null;
      try
      {
        //Channel channel = SocketChannelMap.getSocketChannelByIMEI(sendTask.getDeviceInfoEntity().getDeviceImei());
        Channel channel = NettyChannelMapUtils.get(sendTask.getAppId(), sendTask.getDeviceInfoEntity().getDeviceImei());
        //log.debug(logHeader + "[推送]appid:" + sendTask.getAppId() + "[推送]设备IMEI:" + sendTask.getDeviceInfoEntity().getDeviceImei());
        if (channel == null)
        {
          // 如果设备一直不在线，那么超时还是需要把该请求丢弃掉
          // 判断是否超时，这里表示已经超时
          if (new Date().getTime() > sendTask.getReSendInfo().getExpireTime())
          {
            log.error(logHeader + THREAD_NAME + "[推送]在规定时间内设备一直不在线，该发送任务已被丢弃。");
            continue;
          }

          log.error(logHeader + THREAD_NAME + "[推送]向设备发送指令失败，设备不在线。重新进入队列，等待下次再处理。");

          boolean result = TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
          if (!result)
          {
            log.error(logHeader + THREAD_NAME + "[推送]向设备发送指令失败，设备不在线。重新放入队列时，进队列失败。该发送任务已被丢弃。");
          }
          continue;
        }

        channelId = channel.id().asShortText();
        remoteIpAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
        logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";
        sendJson = sendTask.getSendJson();

//        // 判断设备是否支持重发
//        if (DictEnum.ReSend.NO == sendTask.getDeviceInfoEntity().getResendMode())
//        {
//          // 不支持重发
//          // 发送数据
//          log.debug(logHeader + THREAD_NAME + "[推送]不支持重发");
//          sendJsonPacket(channel, sendJson, sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
//          // 继续下一个任务
//          continue;
//        }

        // 判断指令是否需要重发，目前仅支持启动指令重发，其他指令都不重发
        short commandId = sendTask.getBasePacket().getCommandId();
        if (DeviceCommand.START_DEVICE_REQ != commandId)
        {
          // 不支持重发
          // 发送数据
          sendJsonPacket(channel, sendJson, sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          // 继续下一个任务
          continue;
        }
        
        // 支持重发，开始重发处理
        if (sendTask.getReSendInfo().getReSendCount() == 0)
        {
          // 第一次发送
          log.debug(logHeader + THREAD_NAME + "[推送]支持重发，设备<"+ sendTask.getDeviceInfoEntity().getDeviceImei() +">第["+ sendTask.getReSendInfo().getReSendCount() +"]次发送指令消息！");
          sendJsonPacket(channel, sendJson, sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          sendTask.setReSend();// 计数器加一，并同时更新上次发送的时间
          TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程,重新进队列，下次会判断是否需要重发
          continue;
        }
        else
        {
          // 非第一次发送
        	String deviceIMEI = sendTask.getDeviceInfoEntity().getDeviceImei();
        	//模块在收到启动指令后开始等待20s，若设备在此期间还未启动成功，则发送最后一次启动指令
	        if(startSuccessWaiting) {
	        	if ((new Date().getTime() - sendTask.getReSendInfo().getLastSendTime()) < WAITING_TIME)
	            {
	        		//等待设备启动成功
	        		TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
	        		continue;
	            }else {
	            	// 从redis里取到该设备的信息
					ControlInfoEntity controlInfo = NettyChannelMapUtils.getDeviceControlInfoByIEMI(sendTask.getAppId(), deviceIMEI);
					//获取启动设备通道状态
					Byte channelStatus = controlInfo.getChannelStatus(sendTask.getDeviceChannelId());
					// 设备不处于工作中，则再重发一次指令
					if (channelStatus != DictEnum.DeviceChannelStatusCode.WORKING) {
						log.info(logHeader + THREAD_NAME + "[推送]设备<"+ sendTask.getDeviceInfoEntity().getDeviceImei() +">等待10秒后仍未启动成功，进行最后一次重发。");
						sendJsonPacket(channel, sendJson, sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
					}else {
						log.info(logHeader + THREAD_NAME + "[推送]设备<"+ sendTask.getDeviceInfoEntity().getDeviceImei() +">已启动成功，不需要重发。");
					}
					continue;
	            }
	        }else {
	        	
	            boolean isResponseBack = ReSendResponseInfo.isResponseInfo(deviceIMEI, channelId);// 判断该发送指令的应答是否已经返回
	            if (isResponseBack)
	            {
//	              // 应答包已经返回，不需要再重发。丢弃
	          	  // 应答包已经返回，不需要重发，模块已收到启动指令，开始进入启动成功等待队列
	          	  startSuccessWaiting = true;
	          	  ReSendInfo reSendInfo = sendTask.getReSendInfo(); 
	          	  reSendInfo.setLastSendTime(new Date().getTime());
	          	  sendTask.setReSendInfo(reSendInfo);
	          	  TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
	              log.info(logHeader + THREAD_NAME + "[推送]设备<"+ sendTask.getDeviceInfoEntity().getDeviceImei() +">的应答已经返回，不需要重发。模块已收到启动指令，开始进入启动成功等待队列");
	              continue;
	            }
	            // 判断重发次数超限
	            if (sendTask.getReSendInfo().getReSendCount() > sendTask.getReSendInfo().getMaxReSendCount())
	            {
	              log.debug(logHeader + "[推送]设备<"+ sendTask.getDeviceInfoEntity().getDeviceImei() +">重发请求已达到最大重发次数，已被丢弃。丢弃内容：" + sendJson);
	              continue;
	            }
	            //log.debug(logHeader + "[推送]发送时间："+ (new Date().getTime() - sendTask.getReSendInfo().getLastSendTime()) + ",重发周期：" + ServerConfig.RESEND_INTERVAL);
	            // 判断是否达到间隔重发的时间
	            if ((new Date().getTime() - sendTask.getReSendInfo().getLastSendTime()) < ServerConfig.RESEND_INTERVAL)
	            {
	              // 没到重发时间，重新进队列排队
	          	//log.debug(logHeader + "[推送]发送时间："+ (new Date().getTime() - sendTask.getReSendInfo().getLastSendTime()) +"没到重发时间，重新进队列排队!");
	              TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
	              continue;
	            }
	        }

//          // 判断是否超时，这里表示已经超时
//          if (new Date().getTime() > sendTask.getReSendInfo().getExpireTime())
//          {
//            log.error(logHeader + THREAD_NAME + "[推送]在规定时间内未收到设备应答，该发送任务已被丢弃。");
//            continue;
//          }

          // 开始重发
          log.debug(logHeader + THREAD_NAME + "[重发次数"+ sendTask.getReSendInfo().getReSendCount() +"]向设备<" + sendTask.getDeviceInfoEntity().getDeviceImei() + ">发送指令。指令内容：" + sendJson);
          sendJsonPacket(channel, sendJson, sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          sendTask.setReSend();// 计数器加一，并同时更新上次发送的时间
          TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程//
                                                   // 重新进队列，下次会判断是否需要重发
        }
      }
      catch (Exception e)
      {
        // 发送失败，重新进队列发
        boolean result = TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
        log.error(logHeader + THREAD_NAME + "[推送]向设备发送指令异常，重新放入队列发送。进队列结果：" + result);
        log.error(e.getMessage(), e);
      }
    }
    log.debug(THREAD_NAME + "[推送]线程执行完毕。");
  }

  /**
   * 发送数据包
   * 
   * @param ctx
   * @param packet
   */
  private void sendPacket(Channel sc, BasePacket packet, int sendCount, String imei)
  {
    String channelId = sc.id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) sc.remoteAddress()).getAddress().getHostAddress();
    String logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";
    log.debug(logHeader + THREAD_NAME + "[推送]向设备<" + imei + ">发送指令。指令内容：" + packet.toString());
    packet.setSendCount((short) sendCount);
    sc.writeAndFlush(packet).addListener(new ChannelFutureListener()
    {
      // 数据发送完毕后，触发本事件
      @Override
      public void operationComplete(ChannelFuture future) throws Exception
      {
        log.debug(logHeader + packet.getPacketName() + "[推送]向设备<" + imei + ">发送指令成功。指令内容：" + packet.toString());
      }
    });
  }
  
  /**
   * 发送数据包
   * 
   * @param ctx
   * @param packet
   */
  private void sendJsonPacket(Channel sc, String sendJson, int sendCount, String imei)
  {
    String channelId = sc.id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) sc.remoteAddress()).getAddress().getHostAddress();
    String logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";
    log.debug(logHeader + THREAD_NAME + "[推送]向设备<" + imei + ">发送指令。指令内容：" + sendJson);
    sc.writeAndFlush(sendJson + "\n").addListener(new ChannelFutureListener()
    {
      // 数据发送完毕后，触发本事件
      @Override
      public void operationComplete(ChannelFuture future) throws Exception
      {
        log.debug(logHeader + "[推送]向设备<" + imei + ">发送指令成功。指令内容：" + sendJson);
      }
    });
  }
}
